# /usr/bin/python
# coding: utf-8

import uuid
import hmac
import ujson
import hashlib
import redis

"""
set(name, value, ex=None, px=None, nx=False, xx=False)
     ex，过期时间（秒）
     px，过期时间（毫秒）
     nx，如果设置为True，则只有name不存在时，当前set操作才执行,同setnx(name, value)
     xx，如果设置为True，则只有name存在时，当前set操作才执行'''
"""


class SessionData(dict):
    def __init__(self, session_id, hmac_key):
        self.session_id = session_id
        self.hmac_key = hmac_key


class Session(SessionData):
    def __init__(self, session_manager, request_handler):
        self.session_manager = session_manager
        self.request_handler = request_handler
        current_session = session_manager.get(request_handler)
        for key, data in current_session.items():
            self[key] = data
        self.session_id = current_session.session_id
        self.hmac_key = current_session.hmac_key

    def save(self):
        self.session_manager.set(self.request_handler, self)

    def exit(self):
        self.clear()
        self.save()


class SessionManager(object):
    def __init__(self, secret, store_options, session_timeout):
        """
        一个Tornado实例仅创建一个SessionManager实例
        :param secret: 64字节服务器端密钥
        :param store_options: 设置redis_host，redis_port，redis_pass
        :param session_timeout: session过期时间，单位为秒
        """
        self.secret = secret
        self.session_timeout = session_timeout
        try:
            if store_options['redis_pass']:
                self.redis = redis.StrictRedis(host=store_options['redis_host'], port=store_options['redis_port'],
                                               password=store_options['redis_pass'])
            else:
                self.redis = redis.StrictRedis(host=store_options['redis_host'], port=store_options['redis_port'])
        except Exception as e:
            # 连接redis失败
            print(e)

    def _fetch(self, session_id) -> dict:
        """
        通过session_id获取json串,并刷新过期时间
        :param session_id:
        :return:
        """
        try:
            session_data = raw_data = self.redis.get(session_id)
            if raw_data is not None:
                # 用户还在访问站点，则刷新过期时间
                self.redis.setex(session_id, self.session_timeout, raw_data)
                session_data = ujson.loads(raw_data)

            if type(session_data) is dict:
                return session_data
            else:
                return {}
        except IOError as e:
            print(e)
            return {}

    def get(self, request_handler=None) -> dict:
        """
        若首次访问站点，生成session_id与hmac_key并写入cookie，否则通过句柄获取cookie中的session_id与hmac_key验证无误后从redis获取缓存数据
        :param request_handler: 句柄，例如IndexHandler，句柄为空强制重新登录
        :return: 当前用户的session信息或者空
        """
        if request_handler is None:
            session_id = None
            hmac_key = None
        else:
            session_id = request_handler.get_secure_cookie("session_id")
            hmac_key = request_handler.get_secure_cookie("verification")

        if session_id is None:
            # 用户首次访问，生成session_id与hmac_key,并设置cookie
            session_exists = False
            session_id = self._generate_id()
            hmac_key = self._generate_hmac(session_id)
            request_handler.set_secure_cookie("session_id", session_id, httponly=True)
            request_handler.set_secure_cookie("verification", hmac_key)
        else:
            # 读取到用户访问记录，检测session_id与hmac_key可用性
            session_id = session_id.decode()
            hmac_key = hmac_key.decode()
            session_exists = True
            check_hmac = self._generate_hmac(session_id)
            if hmac_key != check_hmac:
                # 测试客户端hmac值与服务端通过session_id生成的唯一hmac值是否匹配
                raise InvalidSessionException()

        session = SessionData(session_id, hmac_key)
        if session_exists:
            # 从redis获取访问记录
            for key, data in self._fetch(session_id).items():
                session[key] = data
        return session

    def set(self, request_handler, session):
        request_handler.set_secure_cookie("session_id", session.session_id, httponly=True)
        request_handler.set_secure_cookie("verification", session.hmac_key)
        session_data = ujson.dumps(session)
        self.redis.setex(session.session_id, self.session_timeout, session_data)

    def _generate_id(self) -> str:
        # 每次生成一个不同的随机数列，用以指派用户session_id
        new_id = hashlib.sha256(self.secret + uuid.uuid4().bytes)
        return new_id.hexdigest()

    def _generate_hmac(self, session_id) -> str:
        return hmac.new(bytes(str(session_id), 'utf-8'),
                        bytes(str(self.secret), 'utf-8'), hashlib.sha256).hexdigest()


class InvalidSessionException(Exception):
    pass
